﻿#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.IO;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Globalization;
using Yarr;

namespace Yarr.YarrPad
{
    public class Console : Editor
    {
        #region Private members
        private const string INIT_FILE = "yarr-init.lsp";

        private static bool wroteGreeting;
        private static bool outputCaptured;

        private readonly CommandBuffer commandBuffer;
        private readonly CommandHistory commandHistory;

        private static StreamWriter outputWriter;
        private static MemoryStream outputStream;

        private static StreamWriter errorWriter;
        private static MemoryStream errorStream;

        private bool continuation;

        private string prompt1;
        private string promptN;
        #endregion


        #region Properties
        /// <summary>
        /// The initial prompt
        /// </summary>
        public string Prompt1
        {
            get { return prompt1; }
            set { prompt1 = value; }
        }

        /// <summary>
        /// The continuation prompt
        /// </summary>
        public string PromptN
        {
            get { return promptN; }
            set { promptN = value; }
        }

        /// <summary>
        /// The Yarr environment in which to execute commands
        /// </summary>
        public override Yarr.Environment Environment
        {
            get { return base.Environment; }
            set
            {
                base.Environment = value;

                if ( commandBuffer != null )
                    commandBuffer.Environment = Environment;
            }
        }
        #endregion


        #region Constructors
        public Console()
        {
            CaptureConsoleOut();

            commandHistory = new CommandHistory();
            commandBuffer = new CommandBuffer(this.Environment, outputWriter, outputStream,
                                                                errorWriter, errorStream);
            WriteGreeting();

            InitYarr(true);
        }

        public Console(Yarr.Environment environment)
            : base(environment) 
        {
            CaptureConsoleOut();

            commandHistory = new CommandHistory();
            commandBuffer = new CommandBuffer(this.Environment, outputWriter, outputStream,
                                                                errorWriter, errorStream);
            WriteGreeting();

            InitYarr(false);
        }
        #endregion


        #region Public methods
        /// <summary>
        /// Display any output captured from Console.Out and Console.Error
        /// </summary>
        public void WriteOutput()
        {
            lock (outputStream)
            {
                if (outputStream.Length > 0)
                {
                    WriteLine(outputWriter.Encoding.GetString(outputStream.ToArray()), TextType.Output);
                    outputStream.SetLength(0);
                }
            }
            lock (errorStream)
            {
                if (errorStream.Length > 0)
                {
                    WriteLine(errorWriter.Encoding.GetString(errorStream.ToArray()), TextType.Error);
                    errorStream.SetLength(0);
                }
            }
        }
        #endregion


        #region Helper methods
        private void CaptureConsoleOut()
        {
            if (!outputCaptured)
            {
                outputStream = new MemoryStream();
                outputWriter = new StreamWriter(MemoryStream.Synchronized(outputStream));

                errorStream = new MemoryStream();
                errorWriter = new StreamWriter(MemoryStream.Synchronized(errorStream));

                System.Console.SetOut(outputWriter);
                System.Console.SetError(errorWriter);

                outputCaptured = true;
            }
        }

        private void ClearCurrentLine()
        {
            new TextRange(initialPromptEnd, Document.ContentEnd)
                    .ClearAllProperties(); 

            TextPointer start = NextInsertionPosition(promptEnd);
            if (start != null)
                new TextRange(start, Document.ContentEnd).Text = string.Empty;
        }

        private void EnsurePrompt(RoutedEventArgs eventArgs)
        {
            if (CaretPosition.GetOffsetToPosition(promptEnd) >= -1)
                eventArgs.Handled = true;
        }

        private void HandleInput()
        {
            string input = new TextRange(promptEnd, Document.ContentEnd).Text;

            if (!string.IsNullOrEmpty(input))
            {
                commandHistory.AddEntry(input.Trim());
                try
                {
                    CommandResponse result = commandBuffer.Add(input);
                    if (result != null)
                    {
                        this.Environment["?"] = result.Result;

                        if (!String.IsNullOrEmpty(result.Output))
                            WriteLine(result.Output, TextType.Output);

                        if (!String.IsNullOrEmpty(result.Error))
                            WriteLine(result.Error, TextType.Error);

                        WriteLine(Printer.WriteToString(result.Result), TextType.Result);

                        continuation = false;
                    }
                    else
                        continuation = true;
                }
                catch (Exception ex)
                {
                    HandleException(ex);
                    continuation = false;
                }
            }
        }

        private void InitYarr(bool loadInitLisp)
        {
            if (loadInitLisp)
            {
                string initFile = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, INIT_FILE);
                if (File.Exists(initFile))
                {
                    try
                    {
                        Runtime.Load(initFile, null, Environment);
                    }
                    catch (Exception ex)
                    {
                        System.Console.Error.WriteLine("Error Loading Init file: {0}", ex.Message);
                    }
                }
            }

            outputWriter.Flush();
            lock (outputStream)
            {
                if (outputStream.Length > 0)
                {
                    WriteLine(outputWriter.Encoding.GetString(outputStream.ToArray()), TextType.Output);
                    outputStream.SetLength(0);
                }
            }

            errorWriter.Flush();
            lock (errorStream)
            {
                if (errorStream.Length > 0)
                {
                    WriteLine(errorWriter.Encoding.GetString(errorStream.ToArray()), TextType.Error);
                    errorStream.SetLength(0);
                }
            }
        }

        private void WriteGreeting()
        {
            Document.Blocks.Clear();

            if (!wroteGreeting)
            {
                WriteLine("Welcome to Yarr", TextType.Header);
                WriteLine(String.Empty, TextType.Header);
                WriteLine("This program is free software and is distributed", TextType.Header);
                WriteLine("under the terms of the GNU General Public License.", TextType.Header);
                WriteLine(String.Empty, TextType.Header);
                WriteVersion();

                prompt1 = "> ";
                promptN = "* ";

                wroteGreeting = true;
            }
            else
            {
                prompt1 = "eval> ";
                promptN = "eval* ";
            }

            WritePrompt();
        }

        private void WritePrompt()
        {
            string prompt = continuation ? promptN : prompt1;
            WriteLine(prompt, TextType.Prompt);

            promptEnd = CaretPosition.GetNextInsertionPosition(LogicalDirection.Backward);
            if (!continuation)
                initialPromptEnd = promptEnd;

            //set default color
            Write(string.Empty, TextType.Input);
            Focus();
        }

        private void WriteVersion()
        {
            WriteLine(
                String.Format(CultureInfo.CurrentCulture, "Build: {0}\nOS: {1}\nCLR: {2}\n",
                    System.Reflection.Assembly.GetExecutingAssembly().GetName().Version,
                    System.Environment.OSVersion,
                    System.Environment.Version), TextType.Header);
        }
        #endregion


        #region Event handlers
        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            if (!e.Handled)
            {
                switch (e.Key)
                {
                    case Key.Back:
                        EnsurePrompt(e);
                        if (!e.Handled)
                            new TextRange(initialPromptEnd, Document.ContentEnd)
                                .ClearAllProperties();
                        break;

                    case Key.Left:
                    case Key.Right:
                        break;

                    case Key.Up:
                    case Key.Down:
                        if (!MembersPopup.IsOpen)
                        {
                            string command = e.Key == Key.Up ?
                                                        commandHistory.PreviousEntry() :
                                                        commandHistory.NextEntry();
                            if (!String.IsNullOrEmpty(command))
                            {
                                new TextRange(initialPromptEnd, Document.ContentEnd)
                                    .ClearAllProperties();
                                ClearCurrentLine();
                                continuation = false;
                                Write(command, TextType.Input);
                            }
                            e.Handled = true;
                        }
                        break;

                    case Key.Enter:
                        new TextRange(initialPromptEnd, Document.ContentEnd)
                            .ClearAllProperties();
                        HandleInput();
                        WritePrompt();
                        e.Handled = true;
                        break;

                    case Key.X:
                        if (Keyboard.Modifiers == ModifierKeys.Control)
                        {
                            ClearCurrentLine();
                            commandBuffer.Clear();
                            if (continuation)
                            {
                                continuation = false;
                                WritePrompt();
                            }
                            e.Handled = true;
                        }
                        break;

                    default:
                        if (!MembersPopup.IsOpen && CaretPosition.CompareTo(initialPromptEnd) <= 0)
                            e.Handled = true;
                        break;
                }
            }

            base.OnPreviewKeyDown(e);
        }
        #endregion
    }
}